package com.qlogic.commons.platform.impls.ftp;

import java.io.IOException;
import java.io.LineNumberReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.File;

import java.text.SimpleDateFormat;
import java.text.ParsePosition;

import java.net.InetAddress;
import java.util.Date;
import java.util.Vector;
import java.util.Properties;

public class FTPClient {

    private SimpleDateFormat tsFormat =  new SimpleDateFormat("yyyyMMddHHmmss");

    /**
     *  Socket responsible for controlling
     *  the connection
     */
    private FTPControlSocket control = null;

    /**
     *  Socket responsible for transferring
     *  the data
     */
    private FTPDataSocket data = null;

    /**
     *  Socket timeout for both data and control. In
     *  milliseconds
     */
    private int timeout = 0;

    /**
     *  Record of the transfer type - make the default ASCII
     */
    private FTPTransferType transferType = FTPTransferType.ASCII;

    /**
     *  Record of the connect mode - make the default PASV (as this was
     *  the original mode supported)
     */
    private FTPConnectMode connectMode = FTPConnectMode.PASV;

    /**
     *  Holds the last valid reply from the server on the control socket
     */
    private FTPReply lastValidReply;

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
	 * @param remoteHost
	 * @throws IOException
	 * @throws FTPException
     *  @param   remoteHost  the remote hostname
     */
    public FTPClient(String remoteHost)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost,
                                       FTPControlSocket.CONTROL_PORT,
                                       null, 0);
    }

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
	 * @param remoteHost
	 * @param controlPort
	 * @throws IOException
	 * @throws FTPException
     */
    public FTPClient(String remoteHost, int controlPort)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost, controlPort, null, 0);
    }

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
	 * @param remoteAddr
	 * @throws IOException
	 * @throws FTPException
	 */
    public FTPClient(InetAddress remoteAddr)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr,
                                       FTPControlSocket.CONTROL_PORT,
                                       null, 0);
    }

	/**
	 * Method FTPClient.
     * Constructor. Creates the control
     * socket. Allows setting of control port (normally
     * set by default to 21).
	 * @param remoteAddr
	 * @param controlPort
	 * @throws IOException
	 * @throws FTPException
     */
    public FTPClient(InetAddress remoteAddr, int controlPort)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr, controlPort,
                                       null, 0);
    }

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
	 * @param remoteHost
	 * @param log
	 * @param timeout
	 * @throws IOException
	 * @throws FTPException
     */
    public FTPClient(String remoteHost, PrintWriter log, int timeout)
        throws IOException, FTPException {
        control = new FTPControlSocket(remoteHost,
                                       FTPControlSocket.CONTROL_PORT,
                                       log, timeout);
    }

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
	 * @param remoteHost
	 * @param controlPort
	 * @param log
	 * @param timeout
	 * @throws IOException
	 * @throws FTPException
     */
    public FTPClient(String remoteHost, int controlPort,
                     PrintWriter log, int timeout)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteHost, controlPort,
                                       log, timeout);
    }

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
	 * @param remoteAddr
	 * @param log
	 * @param timeout
	 * @throws IOException
	 * @throws FTPException
     */
    public FTPClient(InetAddress remoteAddr, PrintWriter log,
                     int timeout)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr,
                                       FTPControlSocket.CONTROL_PORT,
                                       log, timeout);
    }

	/**
	 * Method FTPClient.
     * Constructeur. creer les sockets controle
     * Allows setting of control port (normally
     * set by default to 21).
	 * @param remoteAddr
	 * @param controlPort
	 * @param log
	 * @param timeout
	 * @throws IOException
	 * @throws FTPException
     */
    public FTPClient(InetAddress remoteAddr, int controlPort,
                     PrintWriter log, int timeout)
        throws IOException, FTPException {

        control = new FTPControlSocket(remoteAddr, controlPort,
                                       log, timeout);
    }

	/**
	 * Method setTimeout.
     * Set the TCP timeout on the underlying socket.
     * If a timeout is set, then any operation which
     * takes longer than the timeout value will be
     * killed with a java.io.InterruptedException. We
     * set both the control and data connections
	 * @param millis
	 * @throws IOException
	 */
    public void setTimeout(int millis)
        throws IOException {

        this.timeout = millis;
        control.setTimeout(millis);
    }

	/**
	 * Method setConnectMode.
	 * @param mode
     */
    public void setConnectMode(FTPConnectMode mode) {
        connectMode = mode;
    }

	/**
	 * Method login.
     *  Login into an account on the FTP server. This
     *  call completes the entire login process
	 * @param user
	 * @param password
	 * @throws IOException
	 * @throws FTPException
     */
    public void login(String user, String password)
        throws IOException, FTPException {

        String response = control.sendCommand("USER " + user);
        lastValidReply = control.validateReply(response, "331");
        response = control.sendCommand("PASS " + password);
        lastValidReply = control.validateReply(response, "230");
    }


	/**
	 * Method user.
     * Supply the user name to log into an account
     * on the FTP server. Must be followed by the
     * password() method - but we allow for
	 * @param user
	 * @throws IOException
	 * @throws FTPException
     */
    public void user(String user)
        throws IOException, FTPException {

        String reply = control.sendCommand("USER " + user);

        // we allow for a site with no password - 230 response
        String[] validCodes = {"230", "331"};
        lastValidReply = control.validateReply(reply, validCodes);
    }


	/**
	 * Method password.
     * Supplies the password for a previously supplied
     * username to log into the FTP server. Must be
     * preceeded by the user() method
	 * @param password
	 * @throws IOException
	 * @throws FTPException
     */
    public void password(String password)
        throws IOException, FTPException {

        String reply = control.sendCommand("PASS " + password);

        // we allow for a site with no passwords (202)
        String[] validCodes = {"230", "202"};
        lastValidReply = control.validateReply(reply, validCodes);
    }

	/**
	 * Method initSOCKS.
     * Set up SOCKS v4/v5 proxy settings. This can be used if there
     * is a SOCKS proxy server in place that must be connected thru.
     * Note that setting these properties directs <b>all</b> TCP
     * sockets in this JVM to the SOCKS proxy
	 * @param port
	 * @param host
     */
    public static void initSOCKS(String port, String host) {
        Properties props = System.getProperties();
        props.put("socksProxyPort", port);
        props.put("socksProxyHost", host);
        System.setProperties(props);
    }

	/**
	 * Method initSOCKSAuthentication.
     * Set up SOCKS username and password for SOCKS username/password
     * authentication. Often, no authentication will be required
     * but the SOCKS server may be configured to request these.
	 * @param username
	 * @param password
     */
    public static void initSOCKSAuthentication(String username,
                                               String password) {
        Properties props = System.getProperties();
        props.put("java.net.socks.username", username);
        props.put("java.net.socks.password", password);
        System.setProperties(props);
    }

	/**
	 * Method getRemoteHostName.
	 * @return String
     */
    String getRemoteHostName() {
        return control.getRemoteHostName();
    }


	/**
	 * Method quote.
     * Issue arbitrary ftp commands to the FTP server.
	 * @param command
	 * @param validCodes
	 * @throws IOException
	 * @throws FTPException
	 */
    public void quote(String command, String[] validCodes)
        throws IOException, FTPException {

        String reply = control.sendCommand(command);

        // allow for no validation to be supplied
        if (validCodes != null && validCodes.length > 0)
            lastValidReply = control.validateReply(reply, validCodes);
    }


	/**
	 * Method put.
	 * @param localPath
     * Put a local file onto the FTP server. It
     * is placed in the current directory.
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
     */
    public void put(String localPath, String remoteFile)
        throws IOException, FTPException {

        put(localPath, remoteFile, false);
    }

	/**
	 * Method put.
     * Put a stream of data onto the FTP server. It
     * is placed in the current directory.
	 * @param srcStream
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
     */
    public void put(InputStream srcStream, String remoteFile)
        throws IOException, FTPException {

        put(srcStream, remoteFile, false);
    }


	/**
	 * Method put.
     * Put a local file onto the FTP server. It
     * is placed in the current directory. Allows appending
     * if current file exists
	 * @param localPath
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
	 */
    public void put(String localPath, String remoteFile,
                    boolean append)
        throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            putASCII(localPath, remoteFile, append);
        }
        else {
            putBinary(localPath, remoteFile, append);
        }
        validateTransfer();
     }

	/**
	 * Method put.
     *  Put a stream of data onto the FTP server. It
     *  is placed in the current directory. Allows appending
     *  if current file exists
	 * @param srcStream
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
	 */
    public void put(InputStream srcStream, String remoteFile,
                    boolean append)
        throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            putASCII(srcStream, remoteFile, append);
        }
        else {
            putBinary(srcStream, remoteFile, append);
        }
        validateTransfer();
    }

	/**
	 * Method validateTransfer.
     *  Validate that the put() or get() was successful
	 * @throws IOException
	 * @throws FTPException
	 */
    private void validateTransfer()
        throws IOException, FTPException {

        // check the control response
        String[] validCodes = {"226", "250"};
        String reply = control.readReply();
        lastValidReply = control.validateReply(reply, validCodes);
    }

	/**
	 * Method initPut.
     *  Request the server to set up the put
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
     */
    private void initPut(String remoteFile, boolean append)
        throws IOException, FTPException {

        // set up data channel
        data = control.createDataSocket(connectMode);
        data.setTimeout(timeout);

        // send the command to store
        String cmd = append ? "APPE " : "STOR ";
        String reply = control.sendCommand(cmd + remoteFile);

        // Can get a 125 or a 150
        String[] validCodes = {"125", "150"};
        lastValidReply = control.validateReply(reply, validCodes);
    }


	/**
	 * Method putASCII.
     *  Put as ASCII, i.e. read a line at a time and write
     *  inserting the correct FTP separator
	 * @param localPath
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
     */
    private void putASCII(String localPath, String remoteFile, boolean append)
        throws IOException, FTPException {

        // create an inputstream & pass to common method
        InputStream srcStream = new FileInputStream(localPath);
        putASCII(srcStream, remoteFile, append);
    }

	/**
	 * Method putASCII.
     * Put as ASCII, i.e. read a line at a time and write
     * inserting the correct FTP separator
	 * @param srcStream
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
	 */
    private void putASCII(InputStream srcStream, String remoteFile,
                          boolean append)
        throws IOException, FTPException {

        // need to read line by line ...
        LineNumberReader in
            = new LineNumberReader(new InputStreamReader(srcStream));

        initPut(remoteFile, append);

        // get an character output stream to write to ... AFTER we
        // have the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        BufferedWriter out =
            new BufferedWriter(
                new OutputStreamWriter(data.getOutputStream()));

        // write line by line, writing \r\n as required by RFC959 after
        // each line
        String line = null;
        while ((line = in.readLine()) != null) {
            out.write(line, 0, line.length());
            out.write(FTPControlSocket.EOL, 0, FTPControlSocket.EOL.length());
        }
        in.close();
        out.flush();
        out.close();

        // and close the data socket
        try {
            data.close();
        }
        catch (IOException ignore) {}
    }


	/**
	 * Method putBinary.
     *  Put as binary, i.e. read and write raw bytes
	 * @param localPath
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
	 */
    private void putBinary(String localPath, String remoteFile,
                           boolean append)
        throws IOException, FTPException {

        // open input stream to read source file ... do this
        // BEFORE opening output stream to server, so if file not
        // found, an exception is thrown
        InputStream srcStream = new FileInputStream(localPath);
        putBinary(srcStream, remoteFile, append);
    }

	/**
	 * Method putBinary.
     *  Put as binary, i.e. read and write raw bytes
	 * @param srcStream
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
	 */
    private void putBinary(InputStream srcStream, String remoteFile,
                           boolean append)
        throws IOException, FTPException {

        BufferedInputStream in =
            new BufferedInputStream(srcStream);

        initPut(remoteFile, append);

        // get an output stream
        BufferedOutputStream out =
            new BufferedOutputStream(
                new DataOutputStream(data.getOutputStream()));

        byte[] buf = new byte[512];

        // read a chunk at a time and write to the data socket
        long size = 0;
        int count = 0;
        while ((count = in.read(buf)) > 0) {
            out.write(buf, 0, count);
            size += count;
        }

        in.close();

        // flush and clean up
        out.flush();
        out.close();

        // and close the data socket
        try {
            data.close();
        }
        catch (IOException ignore) {}

		// log bytes transferred
		control.log("Transferred " + size + " bytes to remote host");
    }


	/**
	 * Method put.
     *  Put data onto the FTP server. It
     *  is placed in the current directory.
	 * @param bytes
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    public void put(byte[] bytes, String remoteFile)
        throws IOException, FTPException {

        put(bytes, remoteFile, false);
    }

	/**
	 * Method put.
     *  Put data onto the FTP server. It
     *  is placed in the current directory. Allows
     *  appending if current file exists
	 * @param bytes
	 * @param remoteFile
	 * @param append
	 * @throws IOException
	 * @throws FTPException
	 */
    public void put(byte[] bytes, String remoteFile, boolean append)
        throws IOException, FTPException {

        initPut(remoteFile, append);

        // get an output stream
        BufferedOutputStream out =
            new BufferedOutputStream(
                new DataOutputStream(data.getOutputStream()));

        // write array
        out.write(bytes, 0, bytes.length);

        // flush and clean up
        out.flush();
        out.close();

        // and close the data socket
        try {
            data.close();
        }
        catch (IOException ignore) {}

        validateTransfer();
    }


	/**
	 * Method get.
     *  Get data from the FTP server. Uses the currently
     *  set transfer mode.
	 * @param localPath
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    public void get(String localPath, String remoteFile)
        throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            getASCII(localPath, remoteFile);
        }
        else {
            getBinary(localPath, remoteFile);
        }
        validateTransfer();
    }

	/**
	 * Method get.
     *  Get data from the FTP server. Uses the currently
     *  set transfer mode.
	 * @param destStream
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    public void get(OutputStream destStream, String remoteFile)
        throws IOException, FTPException {

        // get according to set type
        if (getType() == FTPTransferType.ASCII) {
            getASCII(destStream, remoteFile);
        }
        else {
            getBinary(destStream, remoteFile);
        }
        validateTransfer();
    }


	/**
	 * Method initGet.
     *  Request to the server that the get is set up
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    private void initGet(String remoteFile)
        throws IOException, FTPException {

        // set up data channel
        data = control.createDataSocket(connectMode);
        data.setTimeout(timeout);

        // send the retrieve command
        String reply = control.sendCommand("RETR " + remoteFile);

        // Can get a 125 or a 150
        String[] validCodes1 = {"125", "150"};
        lastValidReply = control.validateReply(reply, validCodes1);
    }


	/**
	 * Method getASCII.
     *  Get as ASCII, i.e. read a line at a time and write
     *  using the correct newline separator for the OS
	 * @param localPath
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    private void getASCII(String localPath, String remoteFile)
        throws IOException, FTPException {

        // B.McKeown:
        // Call initGet() before creating the FileOutputStream.
        // This will prevent being left with an empty file if a FTPException
        // is thrown by initGet().
        initGet(remoteFile);

        // B. McKeown: Need to store the local file name so the file can be
        // deleted if necessary.
        File localFile = new File(localPath);

        // create the buffered stream for writing
        BufferedWriter out =
            new BufferedWriter(
                new FileWriter(localPath));

        // get an character input stream to read data from ... AFTER we
        // have the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        LineNumberReader in =
            new LineNumberReader(
                new InputStreamReader(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // read/write a line at a time
        IOException storedEx = null;
        String line = null;
        try {
            while ((line = in.readLine()) != null) {
                out.write(line, 0, line.length());
                out.newLine();
            }
        }
        catch (IOException ex) {
            storedEx = ex;
            localFile.delete();
        }
        finally {
            out.close();
        }

        try {
            in.close();
            data.close();
        }
        catch (IOException ignore) {}

        // if we failed to write the file, rethrow the exception
        if (storedEx != null)
            throw storedEx;
    }

	/**
	 * Method getASCII.
     *  Get as ASCII, i.e. read a line at a time and write
     *  using the correct newline separator for the OS
	 * @param destStream
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    private void getASCII(OutputStream destStream, String remoteFile)
        throws IOException, FTPException {

        initGet(remoteFile);

        // create the buffered stream for writing
        BufferedWriter out =
            new BufferedWriter(
                new OutputStreamWriter(destStream));

        // get an character input stream to read data from ... AFTER we
        // have the ok to go ahead
        LineNumberReader in =
            new LineNumberReader(
                new InputStreamReader(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // read/write a line at a time
        IOException storedEx = null;
        String line = null;
        try {
            while ((line = in.readLine()) != null) {
                out.write(line, 0, line.length());
                out.newLine();
            }
        }
        catch (IOException ex) {
            storedEx = ex;
        }
        finally {
            out.close();
        }

        try {
            in.close();
            data.close();
        }
        catch (IOException ignore) {}

        // if we failed to write the file, rethrow the exception
        if (storedEx != null)
            throw storedEx;
    }


	/**
	 * Method getBinary.
     *  Get as binary file, i.e. straight transfer of data
	 * @param localPath
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    private void getBinary(String localPath, String remoteFile)
        throws IOException, FTPException {

        // B.McKeown:
        // Call initGet() before creating the FileOutputStream.
        // This will prevent being left with an empty file if a FTPException
        // is thrown by initGet().
        initGet(remoteFile);

        // B. McKeown: Need to store the local file name so the file can be
        // deleted if necessary.
        File localFile = new File(localPath);

        // create the buffered output stream for writing the file
        BufferedOutputStream out =
            new BufferedOutputStream(
                new FileOutputStream(localPath, false));

        // get an input stream to read data from ... AFTER we have
        // the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        BufferedInputStream in =
            new BufferedInputStream(
                new DataInputStream(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // do the retrieving
        long size = 0;
        int chunksize = 4096;
        byte [] chunk = new byte[chunksize];
        int count;
        IOException storedEx = null;

        // read from socket & write to file in chunks
        try {
            while ((count = in.read(chunk, 0, chunksize)) >= 0) {
                out.write(chunk, 0, count);
                size += count;
            }
        }
        catch (IOException ex) {
            storedEx = ex;
            localFile.delete();
        }
        finally {
            out.close();
        }

        // close streams
        try {
            in.close();
            data.close();
        }
        catch (IOException ignore) {}

        // if we failed to write the file, rethrow the exception
        if (storedEx != null)
            throw storedEx;

	    // log bytes transferred
    	control.log("Transferred " + size + " bytes from remote host");
    }

	/**
	 * Method getBinary.
     *  Get as binary file, i.e. straight transfer of data
	 * @param destStream
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    private void getBinary(OutputStream destStream, String remoteFile)
        throws IOException, FTPException {

        initGet(remoteFile);

        // create the buffered output stream for writing the file
        BufferedOutputStream out =
            new BufferedOutputStream(destStream);

        // get an input stream to read data from ... AFTER we have
        // the ok to go ahead AND AFTER we've successfully opened a
        // stream for the local file
        BufferedInputStream in =
            new BufferedInputStream(
                new DataInputStream(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // do the retrieving
		long size = 0;
        int chunksize = 4096;
        byte [] chunk = new byte[chunksize];
        int count;
        IOException storedEx = null;

        // read from socket & write to file in chunks
        try {
            while ((count = in.read(chunk, 0, chunksize)) >= 0) {
                out.write(chunk, 0, count);
				size += count;
            }
        }
        catch (IOException ex) {
            storedEx = ex;
        }
        finally {
            out.close();
        }

        // close streams
        try {
            in.close();
            data.close();
        }
        catch (IOException ignore) {}

        // if we failed to write to the stream, rethrow the exception
        if (storedEx != null)
            throw storedEx;

		// log bytes transferred
		control.log("Transferred " + size + " bytes from remote host");
    }


	/**
	 * Method get.
     *  Get data from the FTP server. Transfers in
     *  whatever mode we are in. Retrieve as a byte array. Note
     *  that we may experience memory limitations as the
     *  entire file must be held in memory at one time.
	 * @param remoteFile
	 * @return byte[]
	 * @throws IOException
	 * @throws FTPException
	 */
    public byte[] get(String remoteFile)
        throws IOException, FTPException {

        initGet(remoteFile);

        // get an input stream to read data from
        BufferedInputStream in =
            new BufferedInputStream(
                new DataInputStream(data.getInputStream()));

        // B. McKeown:
        // If we are in active mode we have to set the timeout of the passive
        // socket. We can achieve this by calling setTimeout() again.
        // If we are in passive mode then we are merely setting the value twice
        // which does no harm anyway. Doing this simplifies any logic changes.
        data.setTimeout(timeout);

        // do the retrieving
        int chunksize = 4096;
        byte [] chunk = new byte[chunksize];  // read chunks into
        byte [] resultBuf = null; // where we place result
        ByteArrayOutputStream temp =
            new ByteArrayOutputStream(chunksize); // temp swap buffer
        int count;  // size of chunk read

        // read from socket & write to file
        while ((count = in.read(chunk, 0, chunksize)) >= 0) {
            temp.write(chunk, 0, count);
        }
        temp.close();

        // get the bytes from the temp buffer
        resultBuf = temp.toByteArray();

        // close streams
        try {
            in.close();
            data.close();
        }
        catch (IOException ignore) {}

        validateTransfer();

        return resultBuf;
    }


	/**
	 * Method site.
     *  Run a site-specific command on the
     *  server. Support for commands is dependent
     *  on the server
	 * @param command
	 * @return boolean
	 * @throws IOException
	 * @throws FTPException
	 */
    public boolean site(String command)
        throws IOException, FTPException {

        // send the retrieve command
        String reply = control.sendCommand("SITE " + command);

        // Can get a 200 (ok) or 202 (not impl). Some
        // FTP servers return 502 (not impl)
        String[] validCodes = {"200", "202", "502"};
        lastValidReply = control.validateReply(reply, validCodes);

        // return true or false? 200 is ok, 202/502 not
        // implemented
        if (reply.substring(0, 3).equals("200"))
            return true;
        else
            return false;
    }


	/**
	 * Method list.
     *  List a directory's contents
	 * @param dirname
	 * @return String
	 * @throws IOException
	 * @throws FTPException
	 */
    public String list(String dirname)
        throws IOException, FTPException {

        return list(dirname, false);
    }


	/**
	 * Method list.
     *  List a directory's contents as one string. A detailed
     *  listing is available, otherwise just filenames are provided.
     *  The detailed listing varies in details depending on OS and
     *  FTP server.
	 * @param dirname
	 * @param full
	 * @return String
	 * @throws IOException
	 * @throws FTPException
	 */
    public String list(String dirname, boolean full)
        throws IOException, FTPException {

        String[] list = dir(dirname, full);

        StringBuffer result = new StringBuffer();
        String sep = System.getProperty("line.separator");

        // loop thru results and make into one string
        for (int i = 0; i < list.length; i++) {
            result.append(list[i]);
            result.append(sep);
        }

        return result.toString();
    }

	/**
	 * Method dir.
     *  List current directory's contents as an array of strings of
     *  filenames.
	 * @return String[]
	 * @throws IOException
	 * @throws FTPException
	 */
    public String[] dir()
        throws IOException, FTPException {

        return dir(null, false);
    }

	/**
	 * Method dir.
     *  List a directory's contents as an array of strings of filenames.
	 * @param dirname
	 * @return String[]
	 * @throws IOException
	 * @throws FTPException
	 */
    public String[] dir(String dirname)
        throws IOException, FTPException {

        return dir(dirname, false);
    }


	/**
	 * Method dir.
     *  List a directory's contents as an array of strings. A detailed
     *  listing is available, otherwise just filenames are provided.
     *  The detailed listing varies in details depending on OS and
     *  FTP server. Note that a full listing can be used on a file
     *  name to obtain information about a file
	 * @param dirname
	 * @param full
	 * @return String[]
	 * @throws IOException
	 * @throws FTPException
	 */
    public String[] dir(String dirname, boolean full)
        throws IOException, FTPException {

        // set up data channel
        data = control.createDataSocket(connectMode);
        data.setTimeout(timeout);

        // send the retrieve command
        String command = full ? "LIST ":"NLST ";
        if (dirname != null)
            command += dirname;

        // some FTP servers bomb out if NLST has whitespace appended
        command = command.trim();
        String reply = control.sendCommand(command);

        // check the control response. wu-ftp returns 550 if the
        // directory is empty, so we handle 550 appropriately. Similarly
        // proFTPD returns 450
        String[] validCodes1 = {"125", "150", "450", "550"};
        lastValidReply = control.validateReply(reply, validCodes1);

        // an empty array of files for 450/550
        String[] result = new String[0];

        // a normal reply ... extract the file list
        String replyCode = lastValidReply.getReplyCode();
        if (!replyCode.equals("450") && !replyCode.equals("550")) {
            // get an character input stream to read data from .
            LineNumberReader in =
                new LineNumberReader(
                     new InputStreamReader(data.getInputStream()));

            // read a line at a time
            Vector lines = new Vector();
            String line = null;
            while ((line = in.readLine()) != null) {
                lines.add(line);
            }
            try {
                in.close();
                data.close();
            }
            catch (IOException ignore) {}

            // check the control response
            String[] validCodes2 = {"226", "250"};
            reply = control.readReply();
            lastValidReply = control.validateReply(reply, validCodes2);

            // empty array is default
            if (!lines.isEmpty())
                result = (String[])lines.toArray(result);
        }
        return result;
    }

	/**
	 * Method getLastValidReply.
	 * @return FTPReply
	 */
    public FTPReply getLastValidReply() {
        return lastValidReply;
    }


	/**
	 * Method debugResponses.
     *  Switch debug of responses on or off
	 * @param on
	 */
    public void debugResponses(boolean on) {
        control.debugResponses(on);
    }

	/**
	 * Method setLogStream.
	 * @param log
	 */
     public void setLogStream(PrintWriter log) {
         control.setLogStream(log);
     }

	/**
	 * Method getType.
	 * @return FTPTransferType
	 */
    public FTPTransferType getType() {
        return transferType;
    }

	/**
	 * Method setType.
	 * @param type
	 * @throws IOException
	 * @throws FTPException
	 */
    public void setType(FTPTransferType type)
        throws IOException, FTPException {

        // determine the character to send
        String typeStr = FTPTransferType.ASCII_CHAR;
        if (type.equals(FTPTransferType.BINARY))
            typeStr = FTPTransferType.BINARY_CHAR;

        // send the command
        String reply = control.sendCommand("TYPE " + typeStr);
        lastValidReply = control.validateReply(reply, "200");

        // record the type
        transferType = type;
    }


	/**
	 * Method delete.
	 * @param remoteFile
	 * @throws IOException
	 * @throws FTPException
	 */
    public void delete(String remoteFile)
        throws IOException, FTPException {

        String reply = control.sendCommand("DELE " + remoteFile);
        lastValidReply = control.validateReply(reply, "250");
    }


	/**
	 * Method rename.
	 * @param from
	 * @param to
	 * @throws IOException
	 * @throws FTPException
	 */
    public void rename(String from, String to)
        throws IOException, FTPException {

        String reply = control.sendCommand("RNFR " + from);
        lastValidReply = control.validateReply(reply, "350");

        reply = control.sendCommand("RNTO " + to);
        lastValidReply = control.validateReply(reply, "250");
    }


	/**
	 * Method rmdir.
	 * @param dir
	 * @throws IOException
	 * @throws FTPException
	 */
    public void rmdir(String dir)
        throws IOException, FTPException {

        String reply = control.sendCommand("RMD " + dir);

        // some servers return 257, technically incorrect but
        // we cater for it ...
        String[] validCodes = {"250", "257"};
        lastValidReply = control.validateReply(reply, validCodes);
    }


	/**
	 * Method mkdir.
	 * @param dir
	 * @throws IOException
	 * @throws FTPException
	 */
    public void mkdir(String dir)
        throws IOException, FTPException {

        String reply = control.sendCommand("MKD " + dir);
        lastValidReply = control.validateReply(reply, "257");
    }


	/**
	 * Method chdir.
	 * @param dir
	 * @throws IOException
	 * @throws FTPException
	 */
    public void chdir(String dir)
        throws IOException, FTPException {

        String reply = control.sendCommand("CWD " + dir);
        lastValidReply = control.validateReply(reply, "250");
    }

	/**
	 * Method chdir.
	 * @param dir
	 * @throws IOException
	 * @throws FTPException
	 */
	public void cd (String dir)
		throws IOException, FTPException {

		String reply = control.sendCommand("CD " + dir);
		lastValidReply = control.validateReply(reply, "257");
	}

	/**
	 * Method modtime.
	 * @param remoteFile
	 * @return Date
	 * @throws IOException
	 * @throws FTPException
	 */
    public Date modtime(String remoteFile)
        throws IOException, FTPException {

        String reply = control.sendCommand("MDTM " + remoteFile);
        lastValidReply = control.validateReply(reply, "213");

        // parse the reply string ...
        Date ts = tsFormat.parse(lastValidReply.getReplyText(),
                                 new ParsePosition(0));
        return ts;
    }

	/**
	 * Method pwd.
	 * @return String
	 * @throws IOException
	 * @throws FTPException
	 */
    public String pwd()
        throws IOException, FTPException {

        String reply = control.sendCommand("PWD");
        lastValidReply = control.validateReply(reply, "257");

        // get the reply text and extract the dir
        // listed in quotes, if we can find it. Otherwise
        // just return the whole reply string
        String text = lastValidReply.getReplyText();
        int start = text.indexOf('"');
        int end = text.lastIndexOf('"');
        if (start >= 0 && end > start)
            return text.substring(start+1, end);
        else
            return text;
    }

	/**
	 * Method system.
	 * @return String
	 * @throws IOException
	 * @throws FTPException
	 */
    public String system()
        throws IOException, FTPException {

        String reply = control.sendCommand("SYST");
        lastValidReply = control.validateReply(reply, "215");
        return lastValidReply.getReplyText();
    }

	/**
	 * Method help.
	 * @param command
	 * @return String
	 * @throws IOException
	 * @throws FTPException
	 */
    public String help(String command)
        throws IOException, FTPException {

        String reply = control.sendCommand("HELP " + command);
        String[] validCodes = {"211", "214"};
        lastValidReply = control.validateReply(reply, validCodes);
        return lastValidReply.getReplyText();
    }

	/**
	 * Method quit.
	 * @throws IOException
	 * @throws FTPException
	 */
    public void quit()
        throws IOException, FTPException {

        try {
            String reply = control.sendCommand("QUIT");
            String[] validCodes = {"221", "226"};
            lastValidReply = control.validateReply(reply, validCodes);
        }
        finally { // ensure we clean up the connection
            control.logout();
            control = null;
        }
    }

}



